// 版权所有2012 Go作者。版权所有。
// 此源代码的使用受BSD样式的约束
// 可以在许可证文件中找到的许可证。

// 包格式实现Go源的标准格式。
// None
// 请注意，Go源代码的格式会随着时间的推移而变化，因此工具依赖于
// 一致的格式应执行特定版本的gofmt二进制文件
// 而不是使用这个包。这样，格式将是稳定的，并且
// 不需要每次gofmt更改时都重新编译这些工具。
// None
// 例如，直接使用此包的预提交检查将表现为
// 根据每个开发人员使用的Go版本不同，导致
// 检查是否天生易碎。
package format

import (
	"bytes"
	"fmt"
	"go/ast"
	"go/parser"
	"go/printer"
	"go/token"
	"io"
)

// 保持这些与cmd/gofmt/gofmt.go同步。
const (
	tabWidth    = 8
	printerMode = printer.UseSpaces | printer.TabIndent | printerNormalizeNumbers

	// printerNormalizeNumbers意味着规范化数字文字前缀
	// 和印刷时的指数。请参阅https:
	// None
	// 该值在go/printer中定义，专门用于go/format和cmd/gofmt。
	printerNormalizeNumbers = 1 << 30
)

var config = printer.Config{Mode: printerMode, Tabwidth: tabWidth}

const parserMode = parser.ParseComments

// 节点以规范的gofmt样式格式化节点，并将结果写入dst。
// None
// 节点类型必须为*ast.File、*printer.CommentedNode、[]ast.Decl、，
// []ast.Stmt，或与ast.Expr、ast.Decl、ast.Spec兼容的赋值，
// 或ast.Stmt。节点不修改节点。导入未按顺序进行排序
// 表示部分源文件的节点（例如，如果节点是
// 不是*ast.File或*printer.CommentedNode（不包装*ast.File）。
// None
// 函数可能提前返回（在写入整个结果之前）
// 并返回一个格式化错误，例如由于错误的AST。
// None
func Node(dst io.Writer, fset *token.FileSet, node interface{}) error {
	// 确定是否有完整的源文件（file！=nil）。
	var file *ast.File
	var cnode *printer.CommentedNode
	switch n := node.(type) {
	case *ast.File:
		file = n
	case *printer.CommentedNode:
		if f, ok := n.Node.(*ast.File); ok {
			file = f
			cnode = n
		}
	}

	// 如有必要，对导入进行排序。
	if file != nil && hasUnsortedImports(file) {
		// 制作AST的副本，因为AST.SortImports具有破坏性。
		// TODO（gri）可以更有效地执行此操作。
		var buf bytes.Buffer
		err := config.Fprint(&buf, fset, file)
		if err != nil {
			return err
		}
		file, err = parser.ParseFile(fset, "", buf.Bytes(), parserMode)
		if err != nil {
			// 我们不应该到这里。如果我们这样做，提供良好的诊断。
			return fmt.Errorf("format.Node internal error (%s)", err)
		}
		ast.SortImports(fset, file)

		// 将新文件与已排序的导入一起使用。
		node = file
		if cnode != nil {
			node = &printer.CommentedNode{Node: file, Comments: cnode.Comments}
		}
	}

	return config.Fprint(dst, fset, node)
}

// 源以规范的gofmt样式格式化src并返回结果
// 或（I/O或语法）错误。src在语法上是一个
// 正确的Go源文件，或Go声明或语句列表。
// None
// 如果src是部分源文件，则src的前导和尾随空格
// 应用于结果（使其具有相同的前导和尾随
// 空格为src），结果的缩进量与第一个缩进量相同
// 包含代码的src行。导入未针对部分源文件进行排序。
// None
func Source(src []byte) ([]byte, error) {
	fset := token.NewFileSet()
	file, sourceAdj, indentAdj, err := parse(fset, "", src, true)
	if err != nil {
		return nil, err
	}

	if sourceAdj == nil {
		// 完整的源文件。
		// ToDo（GRI）总是考虑这样做。
		ast.SortImports(fset, file)
	}

	return format(fset, file, sourceAdj, indentAdj, src, config)
}

func hasUnsortedImports(file *ast.File) bool {
	for _, d := range file.Decls {
		d, ok := d.(*ast.GenDecl)
		if !ok || d.Tok != token.IMPORT {
			// 不是进口申报单，所以我们结束了。
			// 进口总是第一位的。
			return false
		}
		if d.Lparen.IsValid() {
			// 现在假设所有分组的导入都未排序。
			// TODO（gri）应检查它们是否已排序。
			return true
		}
		// 默认情况下，将对未分组的导入进行排序。
	}
	return false
}
